#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
#  Eizelle - fertility awareness calendar for the GNOME desktop
#
#  Copyright (c) 2014 Jonathon Conte
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

import os
import tempfile
import unittest
from gi.repository import GLib
import eizelle
from eizelle import EizelleError

# encrypted journal password is 'secret'
encrypted_journal_data='{"version": 0, "journal_data": "NJsyLJN2sx+/kubLvFgny/cWp+akDXm4M+5M4YKt8FKGGzA3d4gjdu05Zft/Cy1xV69VJtfk5KGz6tl9hu0SJXE7lvYA7VyS1EFXvBmutlvEujMTeJzyqUgnbCpUvcNGzQ8rMY29EgVtnjIp6AcCkA==", "encryption": {"iv": "BHPV099Ds1tYcRAy1Mee5Q==", "version": 0, "hash": "2f053045ac78337341891e70dcc40e032aeeb1a6b8285ea90fb737be2f6bbb39", "salt": "oRzzXSTpGIB/KnHAIuozsGA0fEcdqnsxWL85sWitg0w=", "derivation_iterations": 100}}'
empty_journal_data='{"version": 0, "encryption": null, "journal_data": {"version": 0, "journal_entries": []}}'
full_journal_data='{"version": 0, "encryption": null, "journal_data": {"version": 0, "journal_entries": [{"type_id": "CONCEPTION", "date": {"month": 12, "year": 2010, "day": 7}}, {"note": "\u5831\u3065\u3066\u66ae\u5065\u3069\u3044\u5207\u6771\u3050\u3086\u8457\u5a66\u5831\u3065\u3066\u66ae\u5065\u3069\u3044\u5207\u6771\u3050\u3086\u8457\u5a66", "type_id": "NOTE", "date": {"month": 8, "year": 2011, "day": 30}}, {"type_id": "TABLET", "date": {"month": 10, "year": 2009, "day": 21}}, {"type_id": "TABLET", "date": {"month": 5, "year": 2009, "day": 6}}, {"type_id": "TABLET", "date": {"month": 8, "year": 2009, "day": 26}}, {"type_id": "TABLET", "date": {"month": 4, "year": 2009, "day": 1}}, {"type_id": "TABLET", "date": {"month": 6, "year": 2009, "day": 3}}, {"type_id": "TABLET", "date": {"month": 9, "year": 2009, "day": 23}}, {"type_id": "TABLET", "date": {"month": 4, "year": 2009, "day": 29}}, {"type_id": "TABLET", "date": {"month": 7, "year": 2009, "day": 1}}, {"type_id": "TABLET", "date": {"month": 7, "year": 2009, "day": 29}}, {"type_id": "CYCLE", "date": {"month": 4, "year": 2013, "day": 24}}, {"type_id": "CYCLE", "date": {"month": 8, "year": 2014, "day": 14}}, {"type_id": "CYCLE", "date": {"month": 11, "year": 2013, "day": 6}}, {"type_id": "CYCLE", "date": {"month": 7, "year": 2014, "day": 17}}, {"type_id": "CYCLE", "date": {"month": 6, "year": 2010, "day": 14}}, {"type_id": "CYCLE", "date": {"month": 12, "year": 2013, "day": 5}}, {"type_id": "CYCLE", "date": {"month": 10, "year": 2011, "day": 4}}, {"type_id": "CYCLE", "date": {"month": 8, "year": 2010, "day": 9}}, {"type_id": "CYCLE", "date": {"month": 3, "year": 2013, "day": 27}}, {"type_id": "CYCLE", "date": {"month": 5, "year": 2010, "day": 19}}, {"type_id": "CYCLE", "date": {"month": 2, "year": 2013, "day": 27}}, {"type_id": "CYCLE", "date": {"month": 4, "year": 2010, "day": 21}}, {"type_id": "CYCLE", "date": {"month": 5, "year": 2012, "day": 23}}, {"type_id": "CYCLE", "date": {"month": 8, "year": 2012, "day": 15}}, {"type_id": "CYCLE", "date": {"month": 10, "year": 2010, "day": 3}}, {"type_id": "CYCLE", "date": {"month": 7, "year": 2010, "day": 12}}, {"type_id": "CYCLE", "date": {"month": 11, "year": 2011, "day": 1}}, {"type_id": "CYCLE", "date": {"month": 9, "year": 2010, "day": 6}}, {"type_id": "CYCLE", "date": {"month": 1, "year": 2014, "day": 30}}, {"type_id": "CYCLE", "date": {"month": 11, "year": 2012, "day": 7}}, {"type_id": "CYCLE", "date": {"month": 3, "year": 2014, "day": 27}}, {"type_id": "CYCLE", "date": {"month": 2, "year": 2014, "day": 27}}, {"type_id": "CYCLE", "date": {"month": 5, "year": 2013, "day": 22}}, {"type_id": "CYCLE", "date": {"month": 11, "year": 2014, "day": 7}}, {"type_id": "CYCLE", "date": {"month": 6, "year": 2014, "day": 19}}, {"type_id": "CYCLE", "date": {"month": 11, "year": 2011, "day": 30}}, {"type_id": "CYCLE", "date": {"month": 12, "year": 2009, "day": 29}}, {"type_id": "CYCLE", "date": {"month": 1, "year": 2014, "day": 2}}, {"type_id": "CYCLE", "date": {"month": 9, "year": 2011, "day": 6}}, {"type_id": "CYCLE", "date": {"month": 7, "year": 2013, "day": 17}}, {"type_id": "CYCLE", "date": {"month": 12, "year": 2009, "day": 1}}, {"type_id": "CYCLE", "date": {"month": 12, "year": 2012, "day": 5}}, {"type_id": "CYCLE", "date": {"month": 1, "year": 2012, "day": 25}}, {"type_id": "CYCLE", "date": {"month": 6, "year": 2012, "day": 20}}, {"type_id": "CYCLE", "date": {"month": 9, "year": 2012, "day": 12}}, {"type_id": "CYCLE", "date": {"month": 12, "year": 2014, "day": 5}}, {"type_id": "CYCLE", "date": {"month": 9, "year": 2014, "day": 11}}, {"type_id": "CYCLE", "date": {"month": 1, "year": 2010, "day": 26}}, {"type_id": "CYCLE", "date": {"month": 4, "year": 2012, "day": 25}}, {"type_id": "CYCLE", "date": {"month": 1, "year": 2013, "day": 2}}, {"type_id": "CYCLE", "date": {"month": 10, "year": 2014, "day": 10}}, {"type_id": "CYCLE", "date": {"month": 5, "year": 2014, "day": 22}}, {"type_id": "CYCLE", "date": {"month": 12, "year": 2011, "day": 28}}, {"type_id": "CYCLE", "date": {"month": 4, "year": 2014, "day": 24}}, {"type_id": "CYCLE", "date": {"month": 1, "year": 2013, "day": 30}}, {"type_id": "CYCLE", "date": {"month": 7, "year": 2012, "day": 18}}, {"type_id": "CYCLE", "date": {"month": 9, "year": 2013, "day": 11}}, {"type_id": "CYCLE", "date": {"month": 11, "year": 2010, "day": 28}}, {"type_id": "CYCLE", "date": {"month": 2, "year": 2010, "day": 24}}, {"type_id": "CYCLE", "date": {"month": 3, "year": 2012, "day": 28}}, {"type_id": "CYCLE", "date": {"month": 10, "year": 2012, "day": 10}}, {"type_id": "CYCLE", "date": {"month": 6, "year": 2013, "day": 19}}, {"type_id": "CYCLE", "date": {"month": 3, "year": 2010, "day": 24}}, {"type_id": "CYCLE", "date": {"month": 10, "year": 2010, "day": 31}}, {"type_id": "CYCLE", "date": {"month": 2, "year": 2012, "day": 29}}, {"type_id": "CYCLE", "date": {"month": 10, "year": 2013, "day": 9}}, {"type_id": "CYCLE", "date": {"month": 8, "year": 2013, "day": 14}}]}}'
future_journal_data='{"version": 0, "encryption": null, "journal_data": {"version": 0, "journal_entries": [{"date": {"year": 2014, "day": 13, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 20, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 3, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 12, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 24, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 10, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 17, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 13, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 12, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 22, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 4, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 27, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 17, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 7, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 18, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 11, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 27, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 1, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 2, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 6, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 9, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 29, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 20, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 14, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 17, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 27, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 28, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 7, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 28, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 23, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 17, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 11, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 22, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 23, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 8, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 15, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 19, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 11, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 24, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 25, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 18, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 25, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 26, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 1, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 26, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 2, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 5, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 8, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 19, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 22, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 16, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 6, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 30, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 29, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 9, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 28, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 5, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 30, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 28, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 16, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 30, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 30, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 26, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 16, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 11, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 15, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 6, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 4, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 5, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 19, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 7, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 22, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 21, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 6, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 9, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 4, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 8, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 5, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 25, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 26, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 3, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 23, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 15, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 3, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 29, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 14, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 15, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 27, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 2, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 19, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 9, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 1, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 10, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 31, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 2, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 16, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 13, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 10, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 14, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 23, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 25, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 24, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 20, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 31, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 4, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 12, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 12, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 10, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 14, "month": 6}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 8, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 20, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 21, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 24, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 21, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 29, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 18, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 7, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 1, "month": 7}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 18, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 21, "month": 5}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 13, "month": 4}, "type_id": "BBT", "value": 98.6}, {"date": {"year": 2014, "day": 3, "month": 7}, "type_id": "BBT", "value": 98.6}]}}'

journal_entry_types = (eizelle.JournalEntryTypeKnown.CYCLE, eizelle.JournalEntryTypeKnown.CONCEPTION, eizelle.JournalEntryTypeKnown.NOTE,	eizelle.JournalEntryTypeKnown.TABLET)

class BaseJournal(eizelle.Journal):
	def __init__(self):
		eizelle.Journal.__init__(self)
		self.temp_file = tempfile.NamedTemporaryFile(delete=False)
		self.temp_file.close()

	def __del__(self):
		os.remove(self.temp_file.name)

class EncryptedJournal(BaseJournal):
	def __init__(self):
		BaseJournal.__init__(self)
		with open(self.temp_file.name, "w") as file_:
			file_.write(encrypted_journal_data)
		self.load_from_file(self.temp_file.name)


class EmptyJournal(BaseJournal):
	def __init__(self):
		BaseJournal.__init__(self)
		with open(self.temp_file.name, "w") as file_:
			file_.write(empty_journal_data)
		self.load_from_file(self.temp_file.name)


class FullJournal(BaseJournal):
	def __init__(self):
		BaseJournal.__init__(self)
		with open(self.temp_file.name, "w") as file_:
			file_.write(full_journal_data)
		self.load_from_file(self.temp_file.name)


class FutureJournal(BaseJournal):
	def __init__(self):
		BaseJournal.__init__(self)
		with open(self.temp_file.name, "w") as file_:
			file_.write(future_journal_data)
		self.load_from_file(self.temp_file.name)


class JournalTests(unittest.TestCase):
	def setUp(self):
		# list of all test journals
		self.all_journals = []

		# list of all unencrypted journals
		self.most_journals = []

		# empty journal
		self.empty_journal = EmptyJournal()
		self.all_journals.append(self.empty_journal)
		self.most_journals.append(self.empty_journal)

		# full journal
		self.full_journal = FullJournal()
		self.all_journals.append(self.full_journal)
		self.most_journals.append(self.full_journal)

		# future journal
		self.future_journal = FutureJournal()
		self.all_journals.append(self.future_journal)
		self.most_journals.append(self.future_journal)

		# encrypted journal
		self.encrypted_journal = EncryptedJournal()
		self.all_journals.append(self.encrypted_journal)

	def test_init_cycle_lower_limit(self):
		"""
		Journal.__init__ should reject an invalid cycle_lower_limit
		"""
		# test for wrong type
		for wrong_type in (None, ''):
			self.assertRaises(
				EizelleError,
				eizelle.Journal,
				cycle_lower_limit=wrong_type)

		# test for wrong value
		for wrong_value in (-1,):
			self.assertRaises(
				EizelleError,
				eizelle.Journal,
				cycle_lower_limit=wrong_value)

		# test for valid value
		for value in (0, 1, 27, 28, 30):
			self.assertIsInstance(
				eizelle.Journal(cycle_lower_limit=value),
				eizelle.Journal)

	def test_init_cycle_upper_limit(self):
		"""
		Journal.__init__ should reject an invalid cycle_upper_limit
		"""
		# test for wrong type
		for wrong_type in (None, ''):
			self.assertRaises(
				EizelleError,
				eizelle.Journal,
				cycle_upper_limit=wrong_type)

		# test for wrong value
		for wrong_value in (-1,):
			self.assertRaises(
				EizelleError,
				eizelle.Journal,
				cycle_upper_limit=wrong_value)

		# test for valid value
		for value in (28, 30, 35):
			self.assertIsInstance(
				eizelle.Journal(cycle_upper_limit=value),
				eizelle.Journal)

	def test_get_all_entries(self):
		"""
		Journal.get_all_entries should return an empty list for an
		empty journal.
		"""
		for journal in self.most_journals:
			self.assertIsInstance(journal.get_all_entries(), list)

		entries = self.empty_journal.get_all_entries()
		self.assertEqual(len(entries), 0)

		for journal in (self.full_journal, self.future_journal):
			for entry in journal.get_all_entries():
				self.assertIsInstance(
					entry,
					eizelle.JournalEntry)

	def test_get_average_cycle_length(self):
		"""
		Journal.get_average_cycle_length should return an integer for
		journals containing one or more cycle entries.

		Journal.get_average_cycle_length should return None for journals
		without cycle entries.
		"""

		length = self.empty_journal.get_average_cycle_length()
		self.assertIsNone(length)

		length = self.full_journal.get_average_cycle_length()
		self.assertIsInstance(length, int)
		self.assertEqual(length, 28)

	def test_get_first_entry_journal_entry_type(self):
		"""
		Journal.get_first_entry should reject an invalid
		journal_entry_type.
		"""
		# test for invalid journal entry type
		for journal in self.most_journals:
			for wrong_type in (None, True, '', 0):
				self.assertRaises(
					EizelleError,
					journal.get_first_entry,
					wrong_type)

		# test for valid journal entry type
		for journal_entry_type in eizelle.JournalEntryTypeKnown:
			self.assertIsNone(self.empty_journal.get_first_entry(journal_entry_type))

		for journal_entry_type in journal_entry_types:
			self.assertIsNotNone(self.full_journal.get_first_entry(journal_entry_type))

	def test_get_last_entry_journal_entry_type(self):
		"""
		Journal.get_last_entry should reject an invalid
		journal_entry_type.
		"""
		# test for invalid journal entry type
		for journal in self.most_journals:
			for wrong_type in (None, True, '', 0):
				self.assertRaises(
					EizelleError,
					journal.get_last_entry,
					wrong_type)

		# test for valid journal entry type
		for journal_entry_type in eizelle.JournalEntryTypeKnown:
			self.assertIsNone(journal.get_last_entry(journal_entry_type))

		for journal_entry_type in journal_entry_types:
			self.assertIsNotNone(self.full_journal.get_last_entry(journal_entry_type))

	def test_get_longest_cycle_entry(self):
		"""
		Journal.get_longest_cycle_entry should return None for an empty
		journal.
		"""
		self.assertIsNone(self.empty_journal.get_longest_cycle_entry())
		self.assertIsInstance(
			self.full_journal.get_longest_cycle_entry(),
			eizelle.JournalEntry)

	def test_get_longest_cycle_length(self):
		"""
		Journal.get_longest_cycle_length should return None for an empty
		journal.
		"""
		self.assertIsNone(self.empty_journal.get_longest_cycle_length())
		self.assertIsInstance(
			self.full_journal.get_longest_cycle_length(),
			int)

	def test_get_shortest_cycle_entry(self):
		"""
		Journal.get_shortest_cycle_entry should return None for an empty
		journal.
		"""
		self.assertIsNone(self.empty_journal.get_shortest_cycle_entry())
		self.assertIsInstance(
			self.full_journal.get_shortest_cycle_entry(),
			eizelle.JournalEntry)

	def test_get_shortest_cycle_length(self):
		"""
		Journal.get_shortest_cycle_length should return None for an empty
		journal.
		"""
		self.assertIsNone(self.empty_journal.get_shortest_cycle_length())
		self.assertIsInstance(
			self.full_journal.get_longest_cycle_length(),
			int)

	def test_get_total_entries(self):
		"""
		Journal.get_total_entries should return the number of all
		JournalEntry objects in the journal that are journal_entry_type.

		If journal_entry_type is None, Journal.get_total_entries should
		return the total number of all JournalEntry objects in the
		journal.
		"""
		# test for invalid journal entry type
		for journal in self.most_journals:
			for wrong_type in (True, '', 0):
				self.assertRaises(
					EizelleError,
					journal.get_total_entries,
					wrong_type)

		# test for valid journal entry type
		for journal in self.most_journals:
			for journal_entry_type in eizelle.JournalEntryTypeKnown:
				total = journal.get_total_entries(journal_entry_type)
				self.assertIsInstance(total, int)

		for journal_entry_type in eizelle.JournalEntryTypeKnown:
			total = self.empty_journal.get_total_entries(journal_entry_type)
			self.assertEqual(total, 0)
			total = self.full_journal.get_total_entries(journal_entry_type)
			self.assertGreater(total, 0)

		for journal_entry_type in eizelle.JournalEntryTypeUnknown:
			total = self.empty_journal.get_total_entries(journal_entry_type)
			self.assertEqual(total, 0)
			total = self.future_journal.get_total_entries(journal_entry_type)
			self.assertGreater(total, 0)

		total = self.full_journal.get_total_entries(None)
		self.assertEqual(total, 68)

		total = self.future_journal.get_total_entries(None)
		self.assertEqual(total, 122)

		total = self.empty_journal.get_total_entries(None)
		self.assertEqual(total, 0)

	def test_has_crypto_support(self):
		"""
		Journal.has_crypto_support should return True if libraries
		required for password encryption are installed. Otherwise
		it should return False.
		"""
		journal = self.encrypted_journal
		result = journal.has_crypto_support()
		if eizelle.SHA256 is not None and eizelle.AES is not None:
			self.assertTrue(result)
		else:
			self.assertFalse(result)

	def test_is_encrypted(self):
		"""
		Journal.is_encrypted should return True if an encrypted journal
		was loaded but a password was not provided or if a password
		has been set. Otherwise it should return False.
		"""
		for journal in self.most_journals:
			self.assertFalse(journal.is_encrypted())
			if journal.has_crypto_support() is True:
				journal.set_password("")
				self.assertTrue(journal.is_encrypted())

		self.assertTrue(self.encrypted_journal.is_encrypted())
		if self.encrypted_journal.has_crypto_support() is True:
			self.encrypted_journal.set_password("secret")
			self.encrypted_journal.set_password(None)
			self.assertFalse(self.encrypted_journal.is_encrypted())

	def test_is_locked(self):
		"""
		Journal.is_locked should return True if an ecnrypted journal was
		loaded but a valid password has not been provided. Otherwise
		it should return False.
		"""
		for journal in self.most_journals:
			result = journal.is_locked()
			self.assertFalse(result)

		result = self.encrypted_journal.is_locked()
		self.assertTrue(result)
		if self.encrypted_journal.has_crypto_support() is True:
			self.encrypted_journal.set_password("secret")
			self.encrypted_journal.set_password(None)
			self.assertFalse(self.encrypted_journal.is_locked())

	def test_load_from_file(self):
		"""
		Journal.load_from_file should return None when a valid file
		object is passed and it can be loaded. Otherwise it should
		raise EizelleError.
		"""
		journal = self.empty_journal
		# test for invalid filename type
		for wrong_type in (1, []):
			self.assertRaises(
				EizelleError,
				journal.load_from_file,
				wrong_type)

		# test for invalid filename value
		for wrong_value in ('', ):
			self.assertRaises(
			EizelleError,
			journal.load_from_file,
			wrong_value)

		# test for non-existant file
		self.assertRaises(
			EizelleError,
			journal.load_from_file,
			'non-existant-file')

		# test for valid password value
		journal = self.encrypted_journal
		if journal.has_crypto_support() is True:
			self.assertTrue(journal.is_locked())
			journal.load_from_file(journal.temp_file.name, "secret")
			self.assertFalse(journal.is_locked())

	def test_new_entry(self):
		"""
		Journal.new_entry should reject invalid arguments and return
		None upon successfully adding a JournalEntry object to the
		journal.
		"""
		for journal in self.most_journals:
			# test for invalid date value
			for date in ((0, 0, 0), (-1, -1, -1), (2000, 13, 13), (2000, 2, 31)):
				for journal_entry_type in eizelle.JournalEntryTypeKnown:
					self.assertRaises(
						EizelleError,
						journal.new_entry,
						date,
						journal_entry_type)

			# test for invalid date type
			for date in (None, 0, ''):
				for journal_entry_type in eizelle.JournalEntryTypeKnown:
					self.assertRaises(
						EizelleError,
						journal.new_entry,
						date,
						journal_entry_type)

			# test for invalid journal entry type
			date = (2014, 12, 31)
			for journal_entry_type in (None, 0, ''):
				self.assertRaises(
					EizelleError,
					journal.new_entry,
					date,
					journal_entry_type)

			# test for invalid note entry data
			for note in (1, None):
				self.assertRaises(
					EizelleError,
					journal.new_entry,
					date,
					eizelle.JournalEntryTypeKnown.NOTE)

			# test for valid input
			for date in ((1, 1, 1), (2014, 12, 31), (65535, 12, 31)):
				for journal_entry_type in eizelle.JournalEntryTypeKnown:
					note = ""
					journal.new_entry(date, journal_entry_type, note)


				for note in ("報づて暮健どい切東", "mellow whirled"):
					journal.new_entry(
						date,
						eizelle.JournalEntryTypeKnown.NOTE,
						note)

	def test_remove_entry(self):
		"""
		Journal.remove_entry should reject invalid arguments and return
		None upon successfully removing a JournalEntry object from the
		journal.
		"""
		# test for invalid journal entry
		for invalid_entry in (None, 1, [], ''):
			for journal in self.most_journals:
				self.assertRaises(
					EizelleError,
					journal.remove_entry,
					invalid_entry)

		# test for valid journal entry
		for journal in self.most_journals:
			for entry in journal.get_all_entries():
				journal.remove_entry(entry)

	def test_save_to_file(self):
		"""
		Journal.save_to_file should return True upon
		successfully saving the journal.
		"""
		for journal in self.most_journals:
			self.assertTrue(journal._save_to_file())


		# encrypted journal should not save until it is unlocked
		self.assertFalse(self.encrypted_journal._save_to_file())
		if self.encrypted_journal.has_crypto_support is True:
			self.encrypted_journal.set_password("secret")
			self.assertTrue(self.encrypted_journal._save_to_file())

	def test_set_cycle_lower_limit(self):
		"""
		Journal.set_cycle_lower_limit should reject an invalid
		cycle_lower_limit.
		"""
		for journal in self.most_journals:
			# test for wrong type
			for wrong_type in (None, ''):
				self.assertRaises(
					EizelleError,
					journal.set_cycle_lower_limit,
					wrong_type)

			# test for invalid value
			for wrong_value in (-1,):
				self.assertRaises(
					EizelleError,
					journal.set_cycle_lower_limit,
					wrong_value)

			# test for valid value
			for value in (0, 1, 27, 28, 30):
				self.assertIsNone(journal.set_cycle_lower_limit(value))

	def test_set_cycle_upper_limit(self):
		"""
		Journal.set_cycle_upper_limit should reject an invalid
		cycle_upper_limit.
		"""
		for journal in self.most_journals:
			# test for invalid type
			for wrong_type in (None, ''):
				self.assertRaises(
					EizelleError,
					journal.set_cycle_upper_limit,
					wrong_type)

			# test for invalid value
			for wrong_value in (-1,):
				self.assertRaises(
					EizelleError,
					journal.set_cycle_upper_limit,
					wrong_value)

			# test for valid value
			for value in (28, 30, 35):
				self.assertIsNone(journal.set_cycle_upper_limit(value))

	def test_set_password(self):
		"""
		Journal.set_password should reject an invalid password.
		"""
		for journal in self.most_journals:
			# test for invalid type
			for wrong_type in (1,):
				self.assertRaises(
					EizelleError,
					journal.set_password,
					wrong_type)

			# test for valid value
			if journal.has_crypto_support() is True:
				for value in (None, "", "報づて暮健どい切東", "secret"):
					journal.set_password(value)

		# test unlocking an encrypted journal
		if self.encrypted_journal.has_crypto_support() is True:
			self.encrypted_journal.set_password("secret")
			self.assertFalse(self.encrypted_journal.is_locked())


class JournalEntryTests(unittest.TestCase):
	def setUp(self):
		self.full_journal = FullJournal()
		self.future_journal = FutureJournal()
		self.all_journals = [
			self.full_journal,
			self.future_journal]

	def test_init(self):
		"""
		JournalEntry.__init__ should not accept invalid arguments

		"""
		valid_properties = ({"type_id": "CYCLE", "date": {"day": 1, "month": 1, "year": 2000}},)
		invalid_properties = (
			{},
			None,
			"", 1,
			{"type_id": None},
			{"type_id": "CYCLE", "date": None},
			{"date": {"day": 1, "month": 1, "year": 2000}})
		invalid_journals =	(None, '', [])

		# test for invalid properties
		for journal in self.all_journals:
			for props in invalid_properties:
				self.assertRaises(
					EizelleError,
					eizelle.JournalEntry,
					journal,
					props)

		# test for invalid journals
		for journal in invalid_journals:
			for props in valid_properties:
				self.assertRaises(
					EizelleError,
					eizelle.JournalEntry,
					journal,
					props)

		# test for valid data
		for journal in self.all_journals:
			for props in valid_properties:
				self.assertIsInstance(
					eizelle.JournalEntry(journal, props),
					eizelle.JournalEntry)

	def test_get_date(self):
		"""
		JournalEntry.get_date should return an instance of GLib.Date.
		"""
		for journal in self.all_journals:
			for entry in journal.get_all_entries():
				self.assertIsInstance(
					entry.get_date(),
					GLib.Date)

	def test_get_key(self):
		"""
		JournalEntry.get_key should return a string.
		"""
		for journal in self.all_journals:
			for entry in journal.get_all_entries():
				self.assertIsInstance(
					entry.get_key(),
					str)

	def test_get_note(self):
		"""
		JournalEntry.get_note should return a string or None.
		"""
		for journal in self.all_journals:
			for entry in journal.get_all_entries():
				note = entry.get_note()
				if note is not None:
					self.assertIsInstance(note, str)

	def test_get_properties(self):
		"""
		JournalEntry.get_properties should return dictionary that is
		able to initialize an instance of JournalEntry.
		"""
		for journal in self.all_journals:
			for entry in journal.get_all_entries():
				props = entry.get_properties()
				self.assertIsInstance(
					eizelle.JournalEntry(journal, props),
					eizelle.JournalEntry)

	def test_get_type(self):
		"""
		JournalEntry.get_type should return an instance of
		JournalTypeKnown or JournalTypeUnknown
		"""
		for journal in self.all_journals:
			for entry in journal.get_all_entries():
				entry_type = entry.get_type()
				self.assertTrue(
					type(entry_type) in (
						type(eizelle.JournalEntryTypeKnown),
						type(eizelle.JournalEntryTypeUnknown)))

	def test_get_type_id(self):
		"""
		JournalEntry.get_type_id should return a string.
		"""
		for journal in self.all_journals:
			for entry in journal.get_all_entries():
				type_id = entry.get_type_id()
				self.assertIsInstance(type_id, str)

	def test_get_ymd(self):
		"""
		JournalEntry.get_ymd should return a tuple of three integers
		representing a year, month, and day.
		"""
		for journal in self.all_journals:
			for entry in journal.get_all_entries():
				ymd = entry.get_ymd()
				self.assertIsInstance(ymd, tuple)
				for number in ymd:
					self.assertIsInstance(number, int)

				year, month, day = ymd
				self.assertGreater(year, 0)
				self.assertTrue(1 <= month <= 12)
				self.assertTrue(1 <= day <= 31)

	def test_has_note(self):
		"""
		JournalEntry.has_note should return a boolean value. The value
		should be True if JournalEntry.get_note returns a string. The
		value should be Fales if JournalEntry.get_note returns None.
		"""
		for journal in self.all_journals:
			for entry in journal.get_all_entries():
				has_note = entry.has_note()
				self.assertIsInstance(has_note, bool)
				if has_note is True:
					self.assertIsInstance(entry.get_note(), str)
				else:
					self.assertIsNone(entry.get_note())


if __name__ == "__main__":
	unittest.main()
